import * as assert from "node:assert";
import { describe, it } from "node:test";

import { encodeUtf8 } from "@yume-chan/struct";

import { decodeBase64 } from "../../utils/base64.js";
import type { SimpleRsaPrivateKey } from "../crypto.js";
import { rsaParsePrivateKey } from "../crypto.js";
import { AdbCommand } from "../packet.js";

import type { AdbCredentialManager } from "./packet-processor.js";
import {
    AdbDaemonAuthProcessor,
    AdbDaemonAuthType,
} from "./packet-processor.js";

class MockCredentialManager implements AdbCredentialManager {
    key: SimpleRsaPrivateKey;
    name: string | undefined;

    constructor(key: Uint8Array, name: string | undefined) {
        this.key = rsaParsePrivateKey(key);
        this.name = name;
    }

    *iterateKeys() {
        yield {
            ...this.key,
            name: this.name,
        };
    }

    generateKey() {
        return {
            ...this.key,
            name: this.name,
        };
    }
}

// From `adbkey` generated by Google ADB
const PRIVATE_KEY = decodeBase64(
    `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPlpZTBGI2GL42
XMtFF0jkgVUVQeBcLBzdvKoHhiSbXmbWsxylnWIJHNo94PxNHeic5KQTWImcZQ9L
b9RWshagmqee8Ab6a/6KyP+a5voSHMWiDZmSR3AHV/PsJIUGzE+TlQgxbjq6IRHi
rwGkFo2NmAmUajtI3+eSuq2AZdKj7CVe4BfrcFmMv4//92whEybk0f4Genp0N4qi
EfRCkK96h3Nij3ZvqgvscCV9edFF8j7R4UMIGzNF59aGL0wWIfZiyf/W2frv2H12
lXEc3fsZq9TkU9cIwuXW/yy80XJUJVo2ZPg5z6c9SzLXSy5CCfLA9StnLjtPfuI7
CNm/jpODAgMBAAECggEACNZaU/Jt0+u9vUa6CJjzK3cuDhed246tM+tiOavGRy7/
pcg+QfavQ8AMsnGvjADn9DXvnjs4sIXE3utF2OL//5pV5HhHc8XBMltYNln672Z2
K230ybts04M1CSqM1zs/cAL6NFCDA4WA42ub4EZthEeisMTM/U865o438K1lCEFj
s0+pHWYL6HSirQUYkArIGgwP+PasY8zxkGBl4D3005ZZTjVu4e/c4QCwNn2Vi40T
k/7DmrXm4yQeTrctWVIMxhjWeK2EAkgc9wWRFlpOnCDulP+lnM6wshD8x18muc1j
rLfRbDvDxhVv1BygqJmbj0zOxAmW8mzCsa8gVvhB4QKBgQD1ZRFVpnRnu5ixHwVx
l9NI9n0G9trKQee/OpC5d1bdgYdkOXrBV85STJctvJaitpD5i4H0nLITT5fhIZLL
jfcBJVkXSdwuvqaOBXWRcNmNFDqQtpazXSx1pUuCsW2l9V/gLARJe98uDRVBCVM+
8VvC7hamUNK10GEqg76I+oGlKwKBgQDYjz7Yj5E0M9Trkps3LKLEWMENWvz40vJC
7vDIOSpOnpqtqgW+JKLC53UGgoMEnXQRnAqPbmRj8xmwf9GIAqA2vZGFf5iehCGr
/RiF8RLWxFZohUDYY3B/LDm7/urax7rhiCFbmyWCAsWkllKE8uGOP9eKVOcVjXT3
iZy14FbPCQKBgBrUEuIXUbCpnNb4ekLiA3J9qEujn2XvcKPChmIQfwm2iJPXiOks
bV0oDHsunBVr+kueCfYxT3K3B/bQEdl5SuDwMV5Pb+gYZeMvC5x8BvzaklCR9cXk
UOEH0kqWlVNIkVPT3CAgj9TcD0/N8jD2eD7Ggulp+q9v+b+JKcKWCKiDAoGBAL0N
yKKMKwo0mZOSKDixmeLpTJeZMDEVDvOJ3uAcr6d05LnpLRxCpWibYVluhGx5/IMH
A10V1URATNP9sfEXwcAoHCs8KgNwfGjGCiucOoMNYhXbBrIhlWsgM8LAF00pgicz
jVOIjOUEAIDfzmhMFMb3SvZzik0RceRL2WgZ0g7pAoGAauflDCIaUwJJHEz6u5/w
5XiiqZFzcgzGJAo/ZHQqPbCms/lYGvKFTurg4RSrtpqKB2kVHe1gd65EW9W/JcBQ
U1Q0nKXFxhKY+bz1IqiculhINnAxyBV91s6tdhoazkdSj6XCgtSK9SP/DfiAUWm0
ytnwjm/+s2vme5fFtK9hBKo=
-----END PRIVATE KEY-----`
        .split("\n")
        .slice(1, -1)
        .join(""),
);

// From `adbkey.pub` generated by Google ADB
const PUBLIC_KEY =
    "QAAAANVsDNqDk46/2Qg74n5POy5nK/XA8glCLkvXMks9p885+GQ2WiVUctG8LP/W5cII11Pk1KsZ+90ccZV2fdjv+tnW/8li9iEWTC+G1udFMxsIQ+HRPvJF0Xl9JXDsC6pvdo9ic4d6r5BC9BGiijd0enoG/tHkJhMhbPf/j7+MWXDrF+BeJeyj0mWArbqS599IO2qUCZiNjRakAa/iESG6Om4xCJWTT8wGhSTs81cHcEeSmQ2ixRwS+uaa/8iK/mv6BvCep5qgFrJW1G9LD2WciVgTpOSc6B1N/OA92hwJYp2lHLPWZl6bJIYHqrzdHCxc4EEVVYHkSBdFy1w2vhg2YgRTlpbP00NVrZb6Car8BTqPnwTRIkHBC6nnrg6cWMQ0xusMtxChKBoYGhCLHY4iKK6ra3P1Ou1UXu0WySau3s+Av9FFXxtAuMAJUA+5GSMQGGECRhwLX910OfnHHN+VxqJkHQye4vNhIH5C1dJ39HJoxAdwH2tF7v7GF2fwsy2lUa3Vj6bBssWivCB9cKyJR0GVPZJZ1uah24ecvspwtAqbtxvj7ZD9l7AD92geEJdLrsbfhNaDyAioQ2grI32gdp80su/7BrdAsPaSomxCYBB8opmS+oJq6qTYxNZ0doT9EEyT5D9rl9UXXxq+rQbDpKV1rOQo5zJJ2GkELhUrslFm6n4+JQEAAQA=";

describe("auth", () => {
    describe("AdbDaemonAuthProcessor", () => {
        it("should generate correct public key without name", async () => {
            const store = new MockCredentialManager(
                new Uint8Array(PRIVATE_KEY),
                undefined,
            );

            const authenticator = new AdbDaemonAuthProcessor({
                credentialManager: store,
            });
            const challenge = new Uint8Array(20);

            const first = await authenticator.process({
                command: AdbCommand.Auth,
                arg0: AdbDaemonAuthType.Token,
                arg1: 0,
                payload: challenge,
            });
            // This test focuses on public key authentication, so only check
            // the first response is type Signature and ignore other fields
            assert.strictEqual(first.command, AdbCommand.Auth);
            assert.strictEqual(first.arg0, AdbDaemonAuthType.Signature);

            const result = await authenticator.process({
                command: AdbCommand.Auth,
                arg0: AdbDaemonAuthType.Token,
                arg1: 0,
                payload: challenge,
            });

            assert.deepStrictEqual(result, {
                command: AdbCommand.Auth,
                arg0: AdbDaemonAuthType.PublicKey,
                arg1: 0,
                payload: encodeUtf8(`${PUBLIC_KEY}\0`),
            });
        });

        it("should generate correct public key name", async () => {
            const name = "test@jest";

            const store = new MockCredentialManager(
                new Uint8Array(PRIVATE_KEY),
                name,
            );

            const authenticator = new AdbDaemonAuthProcessor({
                credentialManager: store,
            });
            const challenge = new Uint8Array(20);

            const first = await authenticator.process({
                command: AdbCommand.Auth,
                arg0: AdbDaemonAuthType.Token,
                arg1: 0,
                payload: challenge,
            });
            // This test focuses on public key authentication, so only check
            // the first response is type Signature and ignore other fields
            assert.strictEqual(first.command, AdbCommand.Auth);
            assert.strictEqual(first.arg0, AdbDaemonAuthType.Signature);

            const result = await authenticator.process({
                command: AdbCommand.Auth,
                arg0: AdbDaemonAuthType.Token,
                arg1: 0,
                payload: challenge,
            });

            assert.deepStrictEqual(result, {
                command: AdbCommand.Auth,
                arg0: AdbDaemonAuthType.PublicKey,
                arg1: 0,
                payload: encodeUtf8(`${PUBLIC_KEY} ${name}\0`),
            });
        });
    });
});
